#!/usr/bin/perl
use FindBin;
use lib "$FindBin::Bin";
use lib "$FindBin::Bin/lib";
use lib "$FindBin::Bin/lib/perl-lib/lib/perl5";

package Main;
use strict;
use POSIX;
use Cwd;
use Socket;
use Getopt::Long;
use File::Basename;
use File::Spec::Functions qw(catfile rel2abs);

use AutoExecUtils;

sub usage {
    my $pname = $FindBin::Script;

    print("$pname --OS_USER <OS_USER> --PORT <LISTEN PORT>\n");
    exit(1);
}

sub getProcEnv {
    my ($pid)       = @_;
    my $envMap      = {};
    my $envFilePath = "/proc/$pid/environ";
    if ( -f $envFilePath ) {
        my $content = `cat $envFilePath`;
        my $line;
        foreach $line ( split( /\x0/, $content ) ) {
            if ( $line =~ /^(.*?)=(.*)$/ ) {
                $envMap->{$1} = $2;
            }
        }
    }

    return $envMap;
}

sub getExecutablePath {
    my ($pid)  = @_;
    my @uname  = uname();
    my $ostype = $uname[0];
    $ostype =~ s/\s.*$//;

    my $executablePath;
    if ( $ostype eq 'Windows' ) {
        my $procTxt = `wmic process where "ProcessID=$pid" get ExecutablePath}`;
        my @lines   = split( /\n/, $procTxt );
        if ( scalar(@lines) >= 2 ) {
            $executablePath = $lines[1];
            $executablePath =~ s/^\s*|\s*$//g;
        }
    }
    else {
        if ( -e "/proc/$pid/exe" ) {
            $executablePath = readlink("/proc/$pid/exe");
        }
    }

    return $executablePath;
}

sub parseCommandOpts {
    my ( $pid, $command ) = @_;

    my $opts           = {};
    my @items          = split( /[\s"]+-/, $command );
    my $postmasterPath = $items[0];
    $postmasterPath =~ s/^\s*|\s*$//g;
    $postmasterPath =~ s/^"|"$//g;
    $opts->{postgmasterPath} = $postmasterPath;

    for ( my $i = 1 ; $i < scalar(@items) ; $i++ ) {
        my $optItem = $items[$i];
        if ( $optItem =~ s/^-// ) {

            #--NAME=VALUE
            my ( $name, $value ) = split( /=/, $optItem );
            $opts->{name} = $value;
        }
        elsif ( $optItem =~ /([a-zA-Z])\s(.*)/ ) {
            my $opt    = $1;
            my $optVal = $2;
            if ( $opt eq 'B' ) {
                $opts->{NBUFFERS} = $optVal;
            }
            elsif ( $opt eq 'c' ) {
                my ( $name, $value ) = split( /=/, $optVal );
                $opts->{name} = $value;
            }
            elsif ( $opt eq 'd' ) {
                $opts->{DEBUGGING_LEVEL} = $optVal;
            }
            elsif ( $opt eq 'D' ) {
                $opts->{DATADIR} = $optVal;
            }
            elsif ( $opt eq 'e' ) {
                $opts->{EUROPEAN_DATE} = 1;
            }
            elsif ( $opt eq 'F' ) {
                $opts->{FSYNC_OFF} = 1;
            }
            elsif ( $opt eq 'h' ) {
                $opts->{LISTEN_HOST} = $optVal;
            }
            elsif ( $opt eq 'p' ) {
                $opts->{PORT} = $optVal;
            }
            elsif ( $opt eq 'i' ) {
                $opts->{TCP_ENABLE} = 1;
            }
            elsif ( $opt eq 'k' ) {
                $opts->{SOCKET_LOCATION} = $optVal;
            }
            elsif ( $opt eq 'N' ) {
                $opts->{MAX_CONNECTIONS} = $optVal;
            }
            elsif ( $opt eq 's' ) {
                $opts->{SHOW_STATISTICS} = 1;
            }
            elsif ( $opt eq 'S' ) {
                $opts->{WORK_MEMORY} = $optVal;
            }
        }
    }

    return $opts;
}

sub PGConfParser {
    my ($content) = @_;

    my $pgServerConf = {};
    for my $line ( split( /\n/, $content ) ) {
        $line =~ s/^\s*|\s*$//g;
        if ( $line =~ /^([\w]+)\s*=\s*(.*?)\s*$/ ) {
            my $name = $1;
            my $val  = $2;
            $val =~ s/\s*#.*$//;
            $val =~ s/^['"]|['"]$//g;
            $pgServerConf->{$name} = $val;
        }
    }

    return $pgServerConf;
}

sub getPGConf {
    my ( $port, $osUser ) = @_;

    my $pgConf    = {};
    my $isRunning = 0;
    my $pid;
    my $listenLine = `netstat -nlp |grep :${port} `;
    print("netstat -nlp |grep :${port}\n");
    print( $listenLine, "\n" );
    $listenLine =~ s/^\s*|\s*$//g;
    if ( $listenLine eq '' ) {
        print("WARN: Postgresql not listen on port $port.\n");
    }
    elsif ( $listenLine =~ /LISTEN\s+(\d+)/ ) {
        $pid       = $1;
        $isRunning = 1;
    }

    if ( defined($pid) ) {
        my $procInfoLine = `ps -p $pid -o user,args | tail -1 `;
        $procInfoLine =~ s/^\s*|\s*$//g;
        my $command = $procInfoLine;
        if ( defined($osUser) and $osUser ne '' and $command !~ s/^$osUser\s+// ) {
            print("ERROR: Postgresql listen on port:$port not running on os user:$osUser.\n");
        }
        else {
            $pgConf = parseCommandOpts( $pid, $command );
        }
    }

    $pgConf->{isRunning} = $isRunning;

    my $dataDir = $pgConf->{DATADIR};

    if ( not defined($dataDir) ) {
        my $procEnv = getProcEnv($pid);
        $dataDir = $procEnv->{PGDATA};
    }

    if ( not defined( $pgConf->{SOCKET_LOCATION} ) ) {
        if ( -e "/var/run/postgresql/.s.PGSQL.$port" ) {
            $pgConf->{SOCKET_LOCATION} = "/var/run/postgresql";
        }
        elsif ( -e "/tmp/.s.PGSQL.$port" ) {
            $pgConf->{SOCKET_LOCATION} = "/tmp";
        }
    }
    if ( defined( $pgConf->{SOCKET_LOCATION} ) ) {
        $pgConf->{SOCKET_FILE} = $pgConf->{SOCKET_LOCATION} . "/.s.PGSQL.$port";
    }

    if ( defined($dataDir) and $dataDir ne '' ) {
        $pgConf->{DATADIR} = $dataDir;
        my $walDir = "$dataDir/pg_wal";
        if ( -l $walDir ) {
            $walDir = readlink($walDir);
            my $curDir = Cwd::getcwd();
            chdir($dataDir);
            $walDir = Cwd::abs_path($walDir);
            chdir($curDir);
        }
        $pgConf->{WALDIR} = $walDir;

        if ( defined($dataDir) ) {
            my $pgConfContent = AutoExecUtils::getFileContent("$dataDir/postgresql.conf");
            my $pgServerConf  = PGConfParser($pgConfContent);

            while ( my ( $key, $val ) = each(%$pgServerConf) ) {
                $pgConf->{$key} = $val;
            }
        }
    }

    return $pgConf;
}

sub getDataDirConf {
    my ($dataDir) = @_;

    my $dataDirInfo = {};

    my $mountsMap  = {};
    my $dfTxt      = `mount`;
    my @mountLines = split( /\n/, $dfTxt );
    my $linesCount = scalar(@mountLines);
    for ( my $i = 0 ; $i < $linesCount ; $i++ ) {
        my $line = $mountLines[$i];
        $line =~ s/^\s*|\s*$//g;
        if ( $line =~ /^(.*?)\s+on\s+(.*?)\s+type\s/ ) {
            my $fileSystem = $1;
            my $mountPoint = $2;
            $mountsMap->{$mountPoint} = $fileSystem;
        }
    }

    my $matchedFileSys;
    my $matchedMountPoint;
    my $rel2MountPointPath;
    my $matchLen = 0;

    while ( my ( $mountPoint, $fileSys ) = each(%$mountsMap) ) {
        if ( $dataDir =~ /^$mountPoint/ ) {
            my $mountPointLen = length($mountPoint);
            if ( $mountPointLen > $matchLen ) {
                $matchLen           = $mountPointLen;
                $matchedMountPoint  = $mountPoint;
                $rel2MountPointPath = substr( $dataDir, length($matchedMountPoint) );
                $matchedFileSys     = $fileSys;
            }
        }
    }

    if ( defined($matchedFileSys) ) {
        my $lvPath;
        my $lvName;
        my $vgName;
        print("lvdisplay '$matchedFileSys'\n");
        my $lvTxt = `lvdisplay '$matchedFileSys'`;
        if ( $? == 0 ) {
            print( $lvTxt, "\n" );
            foreach my $line ( split( /\n/, $lvTxt ) ) {
                if ( $line =~ /^\s*LV Path\s+(.*?)$/ ) {
                    $lvPath = $1;
                }
                elsif ( $line =~ /^\s*LV Name\s+(.*?)$/ ) {
                    $lvName = $1;
                }
                elsif ( $line =~ /^\s*VG Name\s+(.*?)$/ ) {
                    $vgName = $1;
                }
            }

            $dataDirInfo->{lvName}         = $lvName;
            $dataDirInfo->{lvMountPoint}   = $matchedMountPoint;
            $dataDirInfo->{rel2MountPoint} = $rel2MountPointPath;
            $dataDirInfo->{vgName}         = $vgName;
            $dataDirInfo->{lvPath}         = $lvPath;
        }
        else {
            print("INFO: Directory:$dataDir is not on a lvm volume.\n");
        }
    }

    return $dataDirInfo;
}

sub main {
    my $opts = {};
    GetOptions(
        $opts, qw{
            OS_USER=s
            PORT=s
        }
    );

    my $hasOptErr = 0;

    my $osUser = $opts->{OS_USER};
    my $port   = $opts->{PORT};

    if ( not defined($port) or $port eq '' ) {
        $hasOptErr = 1;
        print("ERROR: Must defined mysql listen port by option --port.\n");
    }

    if ( $hasOptErr == 1 ) {
        usage();
    }

    my $hasError     = 0;
    my $postgresConf = getPGConf( $port, $osUser );
    my $dataDir      = $postgresConf->{DATADIR};
    if ( defined($dataDir) and $dataDir ne '' ) {
        $postgresConf->{pgHbaConfPath}      = "$dataDir/pg_hba.conf";
        $postgresConf->{pgIdentityConfPath} = "$dataDir/pg_ident.conf";
        my $dataDirInfo = getDataDirConf($dataDir);
        while ( my ( $k, $v ) = each(%$dataDirInfo) ) {

            #为了变量名命名规范，把减号统一换成下划线
            $k =~ s/-/_/g;
            $postgresConf->{$k} = $v;
        }
    }

    $postgresConf->{NODE_HOST}   = $ENV{NODE_HOST};
    $postgresConf->{LISTEN_PORT} = $port;

    AutoExecUtils::saveOutput($postgresConf);
    return $hasError;
}

exit main();

1;
